β

Как стать автором
Курсы по фронтенду
Какой вы ML-специалист: составим портреты типичных ML’щиков

Моя лентаВсе потоки

538.49

Рейтинг

OTUS
Развиваем технологии, обучая их создателей

Подписаться

badcasedaily1 10 авг 2024 в 14:52

Как использовать defer в Go

Простой

5 мин

14K

Блог компании OTUSПрограммирование*Go*

Привет, Хабр!

Defer — это ключевое слово в Go, которое позволяет отложить выполнение функции до момента завершения выполнения текущей функции. Это относительно простой способ управлять ресурсами.

В этой статье мы и рассмотрим как использовать defer в Golang.

Основы

Чтобы понять, как defer работает в Go, нужно знать немного о его внутреннем устройстве. Когда вы используете defer, вы говорите компилятору Go отложить выполнение указанной функции до момента завершения окружающей функции. То естьdefer позволяет управлять ресурсами и гарантировать выполнение определённых действий независимо от того, как будет завершена текущая функция — будь то успешное выполнение или неожиданный выход из-за ошибки.

В Go каждый defer помещает вызов функции в стек отложенных вызовов. Этот стек работает по принципу LIFO, то есть последний добавленный вызов выполняется первым.

Пример кода:

package main

import "fmt"

func main() {
fmt.Println("Начало функции main")

defer fmt.Println("Первый defer")
defer fmt.Println("Второй defer")
defer fmt.Println("Третий defer")

fmt.Println("Конец функции main")
}

Вывод:

Начало функции main
Конец функции main
Третий defer
Второй defer
Первый defer

Как видно из вывода, отложенные вызовы выполняются в обратном порядке по сравнению с их объявлением.

Когда есть несколько отложенных вызовов, важно понимать, что они будут выполнены в обратном порядке. Рассмотрим пример, который демонстрирует это поведение:

package main

import "fmt"

func main() {
defer fmt.Println("Завершение программы")

for i := 1; i <= 3; i++ {
defer fmt.Println("Отложенный вызов номер", i)
}

fmt.Println("Выполнение основного кода")
}

Вывод:

Выполнение основного кода
Отложенный вызов номер 3
Отложенный вызов номер 2
Отложенный вызов номер 1
Завершение программы

defer позволяет структурировать выполнение кода в обратном порядке. Это может быть полезно, например, для поочерёдного закрытия открытых соединений или освобождения ресурсов в порядке, обратном их захвату.

Примеры использования

Управление ресурсами: закрытие файлов, соединений и т. д.

Одно из основных применений defer — это управление ресурсами, которые необходимо закрывать или освобождать после использования. Такие операции, как закрытие файлов или сетевых соединений, идеально подходят для defer, потому что они должны быть выполнены независимо от того, как завершится функция.

Допустим, нужно написать программу для обработки файлов. Вы открываете файл, читаете из него данные, а затем должны его закрыть. Defer позволяет гарантировать, что файл будет закрыт в любом случае:

package main

import (
"bufio"
"fmt"
"os"
)

func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Ошибка при открытии файла:", err)
return
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}

if err := scanner.Err(); err != nil {
fmt.Println("Ошибка чтения файла:", err)
}
}

Здесь defer file.Close() гарантирует, что файл будет закрыт, когда main завершит выполнение, независимо от того, будет ли это нормальный выход или завершение из-за ошибки.

Работа с сетевыми соединениями также иногда требует внимания к освобождению ресурсов:

package main

import (
"fmt"
"net/http"
)

func fetchData(url string) error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("ошибка получения данных: %v", err)
}
defer resp.Body.Close()

// обработка данных из ответа
// ...

return nil
}

func main() {
err := fetchData("https://example.com")
if err != nil {
fmt.Println("Ошибка:", err)
}
}

defer resp.Body.Close() обеспечивает закрытие HTTP-соединения после того, как работа с ним будет закончена.

Очистка ресурсов в panic-ситуациях

Defer также хорошо для обработки ситуаций, когда программа неожиданно "паникует".

Рассмотрим обработку panic-ситуации с defer и recover:

package main

import (
"fmt"
)

func safeDivide(a, b int) int {
defer func() {
if r := recover(); r != nil {
fmt.Println("Восстановление после паники:", r)
}
}()

return a / b
}

func main() {
fmt.Println("Результат деления:", safeDivide(10, 0))
}

defer используется для установки анонимной функции, которая вызывает recover в случае паники.

Логгирование и отладка с использованием defer

Defer также может быть использован для логгирования и отладки.

Иногда бывает полезно записывать в журнал время входа и выхода из функций для отладки или мониторинга:

package main

import (
"fmt"
"time"
)

func logExecutionTime(start time.Time, name string) {
elapsed := time.Since(start)
fmt.Printf("%s заняла %s\n", name, elapsed)
}

func processData() {
defer logExecutionTime(time.Now(), "processData")

// типо обработка данных
time.Sleep(2 * time.Second)
fmt.Println("Данные обработаны")
}

func main() {
processData()
}

defer logExecutionTime(time.Now(), "processData") используется для измерения времени выполнения функции processData.

Подсчёт времени выполнения нескольких функций:

package main

import (
"fmt"
"time"
)

func logFunctionTime() func() {
start := time.Now()
return func() {
fmt.Printf("Время выполнения: %v\n", time.Since(start))
}
}

func funcA() {
defer logFunctionTime()()
time.Sleep(1 * time.Second)
fmt.Println("Выполнение функции A")
}

func funcB() {
defer logFunctionTime()()
time.Sleep(500 * time.Millisecond)
fmt.Println("Выполнение функции B")
}

func main() {
funcA()
funcB()
}

Вывод:

Выполнение функции A
Время выполнения: 1.0004321s
Выполнение функции B
Время выполнения: 500.6542ms

Каждый вызов функции оборачивается в defer, чтобы точно измерить и вывести время её выполнения.

Ресурсные менеджеры

defer может быть в роли "менеджера", который гарантирует, что все ресурсы будут освобождены корректно.

Пример с транзакциями в БД:

package main

import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)

func executeTransaction(db *sql.DB) error {
tx, err := db.Begin()
if err != nil {
return err
}

defer func() {
if err != nil {
tx.Rollback()
fmt.Println("Транзакция откатилась")
} else {
tx.Commit()
fmt.Println("Транзакция завершена")
}
}()

_, err = tx.Exec("INSERT INTO users(name) VALUES('John')")
if err != nil {
return err
}

_, err = tx.Exec("INSERT INTO accounts(user_id) VALUES(LAST_INSERT_ID())")
return err
}

func main() {
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
fmt.Println("Ошибка подключения к базе данных:", err)
return
}
defer db.Close()

err = executeTransaction(db)
if err != nil {
fmt.Println("Ошибка выполнения транзакции:", err)
}
}

defer используется для автоматического отката транзакции в случае ошибки.

Обработка ошибок

Можно использовать defer для обработки ошибок:

package main

import (
"fmt"
"os"
)

func openFile(filename string) (f *os.File, err error) {
f, err = os.Open(filename)
if err != nil {
return nil, err
}

defer func() {
if err != nil {
f.Close()
fmt.Println("Файл закрыт из-за ошибки")
}
}()

// выполнение операций с файлом
return f, nil
}

func main() {
file, err := openFile("example.txt")
if err != nil {
fmt.Println("Ошибка:", err)
return
}
defer file.Close()

fmt.Println("Файл успешно открыт")
}

Доступ к возвращаемым значениям функции

Есть возможность получить доступ к возвращаемым значениям функции внутри defer. Это полезно, когда хочется изменить возвращаемые значения в зависимости от состояния выполнения функции:

package main

import (
"fmt"
)

func calculateSum(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Восстановление после паники:", r)
err = fmt.Errorf("panic occurred")
}
}()

result = a + b
if result > 100 {
panic("Сумма слишком велика")
}

return result, nil
}

func main() {
sum, err := calculateSum(50, 60)
if err != nil {
fmt.Println("Ошибка:", err)
} else {
fmt.Println("Сумма:", sum)
}
}

При правильном использовании defer позволяет писать более чистый, безопасный и управляемый код. Главное — помнить о накладных расходах и избегать типичных ошибок, таких как изменение переменных после их захвата или избыточное использование defer в циклах.

Больше практических инструментов по разным языкам программирования эксперты OTUS рассматривают в рамках практических онлайн-курсов. Подробнее с каталогом курсов можно ознакомиться по ссылке.

Теги:

golang

defer

Хабы:

Блог компании OTUS

Программирование

Go

+11

55

1
+1

OTUS
Развиваем технологии, обучая их создателей

Подписаться

СайтВКонтактеTelegram

103

Карма

239

Рейтинг

artem @badcasedaily1

Motion – Peter Sandberg

Подписаться

Комментарии 1
+1

Публикации

Лучшие за суткиПохожие

A_Tsymbalyuk 13 часов назад

Физика ошибок не прощает. Она догонит тебя на крыше и заставит платить (дважды)

Простой

6 мин

8.1K

Кейс

+40

35

25
+25

BabayMazay 12 часов назад

Самодельные французские радиолампы 1920 г. Часть 3. Разрядная трубка, лампа накаливания

Средний

9 мин

1.5K

Ретроспектива

+33

9

11
+11

Bright_Translate 8 часов назад

Как я от нечего делать замедлил Postgres в 42 000 раз

Средний

11 мин

2.6K

Кейс

Перевод

+27

19

2
+2

butivskiydm 11 часов назад

Моё мини-исследование про продолжительность жизни в России

Простой

2 мин

6.3K

Мнение

+22

17

59
+59

lokosst 12 часов назад

Выделенные серверы + Kubernetes: практическое руководство

17 мин

2.4K

Туториал

+21

23

0

JetHabr 11 часов назад

«Плохие парни работают просто»: пентестеры разбирают реальные кейсы

Средний

9 мин

2.1K

Кейс

+17

19

1
+1

k0mar0v 14 часов назад

HTC Desire HD: история, дизайн и возможности легендарного смартфона

6 мин

2.6K

+17

5

5
+5

MaFrance351 13 часов назад

Домофон Лидер-М. Как скрестить трубку с ключом и всем проиграть

Простой

4 мин

2.5K

+16

5

6
+6

psygalaxy 13 часов назад

Как мы ускорили проверку документации с помощью AI-агента: от боли к решению

Средний

25 мин

1.8K

Обзор

+16

29

9
+9

kubelet 16 часов назад

DNS-хаос, зомби-поды и майнеры в кластере: самые невероятные случаи при работе с Kubernetes

Простой

14 мин

1.2K

Кейс

+16

23

1
+1

Красим папки и распределяем роли: тестируем новую версию менеджера паролей Пассворк 7.0
Турбо

Показать еще

Минуточку внимания

Промо

Неважно, что ты выберешь — с Промокодусом будет дешевле

Событие

Как построить IT-бренд через вирусный контент и юмор

Турбо

Делаем сисадмина сильнее с помощью менеджера паролей

Информация

Сайт

otus.ru

Дата регистрации

22 марта 2017

Дата основания

1 апреля 2017

Численность

101–200 человек

Местоположение

Россия

Представитель

OTUS

Виджет

ВКонтакте

Блог на Хабре

4 часа назад

Модель 3–3–3: как прогнозировать скорость команды и не попадать в цейтнот

395

0

8 часов назад

Всё про std::search и где его применять

1.5K

3
+3

12 часов назад

Три ошибки в C#, которые лучше не допускать

1.3K

2
+2

16 часов назад

Какие тренды должен учитывать CTO в 2025 году

1.4K

2
+2

31 июл в 19:49

Я перешла из Data Science в AI Engineering: вот всё, что вам нужно знать

1.6K

0

Истории

История Яндекс Почты

С Днём сисадмина!

Как расти в ИТ: советы, гайды и опыт сеньоров

1 055 450 очков в облаке – попробуй обогнать

Годнота из блогов компаний

Реклама

АО СЕЛЕКТЕЛ

ИНН 7810962785

erid: 2VSb5zDBFAH

1 000 000 стартапам

Кешбэк 30% на год в Selectel!

Ваш аккаунт

Профиль

Трекер

Диалоги

Настройки

ППА

Разделы

Статьи

Новости

Хабы

Компании

Авторы

Песочница

Информация

Устройство сайта

Для авторов

Для компаний

Документы

Соглашение

Конфиденциальность

Услуги

Корпоративный блог

Медийная реклама

Нативные проекты

Образовательные программы

Стартапам

Настройка языкаТехническая поддержка
© 2006–2025, Habr